﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.Util;
using tessnet2;

namespace QLNXe
{
    public class LicensePlates : DisposableObject
    {
        /// <summary>
        /// The OCR engine
        /// </summary>
        private Tesseract _ocr;

        /// <summary>
        /// Create a license plate detector
        /// </summary>
        public LicensePlates()
        {
            //create OCR engine
            _ocr = new Tesseract();
            //InitDtkEngine();
            try
            {
                _ocr.Init("tessdata", "eng", false);
            }
            catch (Exception ex)
            {
            }
        }

        public Image<Bgr, byte> imgG(Image<Bgr, byte> img)
        {
            using (Image<Gray, byte> gray = img.Convert<Gray, Byte>())

            // Create Canny image
            using (Image<Gray, Byte> canny = new Image<Gray, byte>(gray.Size))
                //CvInvoke.cvCanny(gray, canny, 130, 50, 3);
                return canny.Convert<Bgr, byte>();
        }

        /// <summary>
        /// Detect license plate from the given image
        /// </summary>
        /// <param name="img">The image to search license plate from</param>
        /// <param name="licensePlateImagesList">A list of images where the detected license plate regions are stored</param>
        /// <param name="filteredLicensePlateImagesList">A list of images where the detected license plate regions (with noise removed) are stored</param>
        /// <param name="detectedLicensePlateRegionList">A list where the regions of license plate (defined by an MCvBox2D) are stored</param>
        /// <returns>The list of words for each license plate</returns>
        public List<List<Word>> DetectLicensePlate(
           Image<Bgr, byte> img,
           List<Image<Gray, Byte>> licensePlateImagesList,
           List<Image<Gray, Byte>> filteredLicensePlateImagesList,
           List<MCvBox2D> detectedLicensePlateRegionList)
        {
            List<List<Word>> licenses = new List<List<Word>>();

            // Convert image to gray
            using (Image<Gray, byte> gray = img.Convert<Gray, Byte>())

            // Create Canny image
            using (Image<Gray, Byte> canny = new Image<Gray, byte>(gray.Size))

            //Create MemStorage
            using (MemStorage stor = new MemStorage())
            {
                //Convert gray with Canny Algorithm
                CvInvoke.cvCanny(gray, canny, 130, 70, 3);

                //List all Contour
                Contour<Point> contours = canny.FindContours(
                     Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
                     Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_TREE,
                     stor);

                //Check Contour
                FindLicensePlate(contours, gray, canny, licensePlateImagesList, filteredLicensePlateImagesList, detectedLicensePlateRegionList, licenses);
            }

            return licenses;
        }

        /// <summary>
        /// Check Contour's children
        /// </summary>
        /// <param name="contours"></param>
        /// <returns></returns>
        private int GetNumberOfChildren(Contour<Point> contours)
        {
            Contour<Point> child = contours.VNext;
            if (child == null) return 0;
            int count = 0;
            while (child != null)
            {
                count++;
                child = child.HNext;
            }
            return count;
        }

        private void FindLicensePlate(
           Contour<Point> contours, Image<Gray, Byte> gray, Image<Gray, Byte> canny,
           List<Image<Gray, Byte>> licensePlateImagesList, List<Image<Gray, Byte>> filteredLicensePlateImagesList, List<MCvBox2D> detectedLicensePlateRegionList,
           List<List<Word>> licenses)
        {
            for (; contours != null; contours = contours.HNext)
            {
                int numberOfChildren = GetNumberOfChildren(contours);
                //if it does not contains any children (charactor), it is not a license plate region
                if (numberOfChildren == 0) continue;

                //Check area of licensePlate
                if (contours.Area > 400)
                {
                    if (numberOfChildren < 3)
                    {
                        //If the contour has less than 3 children, it is not a license plate (assuming license plate has at least 3 charactor)
                        //However we should search the children of this contour to see if any of them is a license plate
                        FindLicensePlate(contours.VNext, gray, canny, licensePlateImagesList, filteredLicensePlateImagesList, detectedLicensePlateRegionList, licenses);
                        continue;
                    }

                    MCvBox2D box = contours.GetMinAreaRect();
                    if (box.angle < -45.0)
                    {
                        float tmp = box.size.Width;
                        box.size.Width = box.size.Height;
                        box.size.Height = tmp;
                        box.angle += 90.0f;
                    }
                    else if (box.angle > 45.0)
                    {
                        float tmp = box.size.Width;
                        box.size.Width = box.size.Height;
                        box.size.Height = tmp;
                        box.angle -= 90.0f;
                    }

                    double whRatio = (double)box.size.Width / box.size.Height;
                    if (!(1.0 < whRatio && whRatio < 2.0))
                    {  //if the width height ratio is not in the specific range,it is not a license plate
                        //However we should search the children of this contour to see if any of them is a license plate
                        Contour<Point> child = contours.VNext;
                        if (child != null)
                            FindLicensePlate(child, gray, canny, licensePlateImagesList, filteredLicensePlateImagesList, detectedLicensePlateRegionList, licenses);
                        continue;
                    }
                    //box.size.Width -= 2;
                    //box.size.Height -= 2;
                    Image<Gray, Byte> plate = gray.Copy(box);
                    Image<Gray, Byte> filteredPlate = FilterPlate(plate);

                    List<Word> words = new List<Word>();
                    StringBuilder strBuilder = new StringBuilder();
                    using (Bitmap bmp = filteredPlate.Bitmap)
                    {
                        words = _ocr.DoOCR(plate.Bitmap, plate.ROI);
                        if (words.Count == 0) continue;

                        for (int i = 0; i < words.Count; i++)
                        {
                            strBuilder.Append(words[i].Text);
                        }
                    }

                    licenses.Add(words);
                    licensePlateImagesList.Add(plate);
                    filteredLicensePlateImagesList.Add(filteredPlate);
                    detectedLicensePlateRegionList.Add(box);
                    return;
                }
            }
        }

        /// <summary>
        /// Filter the license plate to remove noise
        /// </summary>
        /// <param name="plate">The license plate image</param>
        /// <returns>License plate image without the noise</returns>
        private static Image<Gray, Byte> FilterPlate(Image<Gray, Byte> plate)
        {
            Image<Gray, Byte> thresh = plate.ThresholdBinaryInv(new Gray(120), new Gray(255));

            using (Image<Gray, Byte> plateMask = new Image<Gray, byte>(plate.Size))
            using (Image<Gray, Byte> plateCanny = plate.Canny(new Gray(100), new Gray(50)))
            using (MemStorage stor = new MemStorage())
            {
                plateMask.SetValue(255.0);
                for (
                   Contour<Point> contours = plateCanny.FindContours(
                      Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
                      Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_EXTERNAL,
                      stor);
                   contours != null; contours = contours.HNext)
                {
                    Rectangle rect = contours.BoundingRectangle;
                    if (rect.Height > (plate.Height >> 1))
                    {
                        rect.X -= 1; rect.Y -= 1; rect.Width += 2; rect.Height += 2;
                        rect.Intersect(plate.ROI);

                        plateMask.Draw(rect, new Gray(0.0), -1);
                    }
                }

                thresh.SetValue(0, plateMask);
            }

            thresh._Erode(1);
            thresh._Dilate(1);

            return thresh;
        }

        /// <summary>
        ///
        /// </summary>
        protected override void DisposeObject()
        {
            _ocr.Dispose();
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="image"></param>
        /// <returns></returns>
        //public string LicensePlate(Image<Gray, Byte> image)
        //{
        //    IntPtr hBitmap = image.Bitmap.GetHbitmap();

        //    engine.ReadFromBitmap((int)hBitmap, 0);

        //    DeleteObject(hBitmap);

        //    string lisence = string.Empty;
        //    if (engine.Plates.Count > 0)
        //    {
        //        for (int i = 0; i < engine.Plates.Count; i++)
        //        {
        //            Plate plate = engine.Plates.get_Item(i);
        //            if (lisence.Length > 0)
        //                lisence += ", ";
        //            lisence += plate.Text;

        //            Marshal.ReleaseComObject(plate);
        //            plate = null;
        //        }

        //        GC.Collect();
        //    }
        //    //if (lisence == "")
        //    //    lisence = "Not found";

        //    return lisence;
        //}

        /// <summary>
        ///
        /// </summary>
        /// <param name="image"></param>
        /// <returns></returns>
        //public string LicensePlate(Image<Bgr, byte> image)
        //{
        //    IntPtr hBitmap = image.Bitmap.GetHbitmap();

        //    engine.ReadFromBitmap((int)hBitmap, 0);

        //    DeleteObject(hBitmap);

        //    string lisence = string.Empty;
        //    if (engine.Plates.Count > 0)
        //    {
        //        for (int i = 0; i < engine.Plates.Count; i++)
        //        {
        //            Plate plate = engine.Plates.get_Item(i);
        //            if (lisence.Length > 0)
        //                lisence += ", ";
        //            lisence += plate.Text;

        //            Marshal.ReleaseComObject(plate);
        //            plate = null;
        //        }

        //        GC.Collect();
        //    }
        //    if (lisence == "")
        //        lisence = "Not found";

        //    return lisence;
        //}

        //public bool InitDtkEngine()
        //{
        //    // Create ANPR Engine object
        //    if (CreateANPREngine(ref engine) != 0)
        //    {
        //        return false;
        //    }
        //    // Initialize engine
        //    try
        //    {
        //        engine.Init(false); // still image mode
        //        engine.SetCountryCode("VN");
        //    }
        //    catch (COMException ex)
        //    {
        //        return false;
        //    }

        //    engine.LicenseManager.AddLicenseKey("Y8GH3GNQ53B0R7URC0I93ID20");
        //    return true;
        //}
    }
}